//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbBindMacros.h>
#include <sqbind/sqbClassHelpers.h>
#include <sqbind/sqbStackUtils.h>
#include <sqbind/sqbTypeInfo.h>

#include "fixtures/SquirrelFixture.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
/// SQBIND_DECLARE_TYPEINFO tests
//----------------------------------------------------------------------------------------------------------------------
struct TypeInfoMacroClass {};
SQBIND_DECLARE_TYPEINFO(TypeInfoMacroClass, TypeInfoMacroClass);

//----------------------------------------------------------------------------------------------------------------------
TEST(MacroTest, TestDeclareTypeInfo)
{
  EXPECT_EQ(sqb::kScriptVarTypeInstance, sqb::TypeInfo<TypeInfoMacroClass>::kTypeID);
  EXPECT_EQ(sizeof(char), sqb::TypeInfo<TypeInfoMacroClass>::kTypeSize);
  EXPECT_EQ(_SC('x'), sqb::TypeInfo<TypeInfoMacroClass>::kTypeMask);
  EXPECT_EQ(SQTrue, sqb::TypeInfo<TypeInfoMacroClass>::kTypeIsInstance);
  EXPECT_STREQ(_SC("TypeInfoMacroClass"), sqb::TypeInfo<TypeInfoMacroClass>().m_typeName);
}

//----------------------------------------------------------------------------------------------------------------------
/// SQBIND_DECLARE_CLASS_BASE tests
//----------------------------------------------------------------------------------------------------------------------
struct UnboundDeclareClassBaseMacroClass {};
struct DeclareClassBaseMacroClass {};
SQBIND_DECLARE_CLASS_BASE2(UnboundDeclareClassBaseMacroClass, UnboundDeclareClassBaseMacroClass);
SQBIND_DECLARE_CLASS_BASE2(DeclareClassBaseMacroClass, DeclareClassBaseMacroClass);

//----------------------------------------------------------------------------------------------------------------------
class DeclareClassBaseFixture : public SquirrelFixture
{
public:
  SQBIND_BEGIN_NONSTANDARD_EXTENSION_BLOCK();

  virtual void SetUp() SQBIND_OVERRIDE
  {
    SquirrelFixture::SetUp();

    ASSERT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));

    sqb::ClassTypeTag<DeclareClassBaseMacroClass> *classTypeTag = sqb::ClassTypeTag<DeclareClassBaseMacroClass>::Get();
    ASSERT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, classTypeTag));

    HSQOBJECT klass;
    ASSERT_SQ_SUCCEEDED(m_vm, sq_getstackobj(m_vm, -1, &klass));
    classTypeTag->SetClassObject(m_vm, klass);

    sq_poptop(m_vm);
  }

  SQBIND_END_NONSTANDARD_EXTENSION_BLOCK();
};

typedef DeclareClassBaseFixture DeclareClassBaseDeathFixture;

//----------------------------------------------------------------------------------------------------------------------
TEST_F(DeclareClassBaseDeathFixture, TestUnboundPush)
{
  UnboundDeclareClassBaseMacroClass instance;

  // check that trying to push an unbound class fails
  //
#if SQBIND_ASSERTS_ENABLED != 0
  EXPECT_DEATH(sqb::Push<UnboundDeclareClassBaseMacroClass>(m_vm, &instance), "");
  EXPECT_DEATH(sqb::Push<UnboundDeclareClassBaseMacroClass>(m_vm, &const_cast<const UnboundDeclareClassBaseMacroClass&>(instance)), "");
#else
  EXPECT_SQ_FAILED(sqb::Push<UnboundDeclareClassBaseMacroClass>(m_vm, &instance));
  EXPECT_SQ_FAILED(sqb::Push<UnboundDeclareClassBaseMacroClass>(m_vm, &const_cast<const UnboundDeclareClassBaseMacroClass&>(instance)));
#endif // SQBIND_ASSERTS_ENABLED != 0
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(DeclareClassBaseFixture, TestPush)
{
  DeclareClassBaseMacroClass instance;

  // push a pointer to instance on the stack
  //
  DeclareClassBaseMacroClass *instance_pointer = &instance;
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, instance_pointer));
  EXPECT_EQ(OT_INSTANCE, sq_gettype(m_vm, -1));

  // check it was pushed correctly and can be retrieved
  //
  SQUserPointer typetag = nullptr;
  EXPECT_SQ_SUCCEEDED(m_vm, sq_gettypetag(m_vm, -1, &typetag));
  EXPECT_EQ(sqb::ClassTypeTag<DeclareClassBaseMacroClass>::Get(), typetag);

  SQUserPointer actual_instance_pointer = nullptr;
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getinstanceup(m_vm, -1, &actual_instance_pointer, nullptr));
  EXPECT_EQ(instance_pointer, actual_instance_pointer);

  sq_poptop(m_vm);

  // push a const pointer to instance on the stack, need to specify the template arg
  // as it gets confused otherwise.
  //
  const DeclareClassBaseMacroClass *const_instance_pointer = &instance;
  EXPECT_SQ_SUCCEEDED(m_vm, sqb::Push<DeclareClassBaseMacroClass>(m_vm, const_instance_pointer));
  EXPECT_EQ(OT_INSTANCE, sq_gettype(m_vm, -1));

  // check it was pushed correctly and can be retrieved
  //
  typetag = nullptr;
  EXPECT_SQ_SUCCEEDED(m_vm, sq_gettypetag(m_vm, -1, &typetag));
  EXPECT_EQ(sqb::ClassTypeTag<DeclareClassBaseMacroClass>::Get(), typetag);

  actual_instance_pointer = nullptr;
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getinstanceup(m_vm, -1, &actual_instance_pointer, nullptr));
  EXPECT_EQ(const_instance_pointer, actual_instance_pointer);

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(DeclareClassBaseFixture, TestMatch)
{
  // check that match doesn't return false positives
  //
#if SQBIND_ASSERTS_ENABLED
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<DeclareClassBaseMacroClass *>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<DeclareClassBaseMacroClass &>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<const DeclareClassBaseMacroClass *>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<const DeclareClassBaseMacroClass &>(), m_vm, -1), "");

  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass *>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass &>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass *>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Match(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass &>(), m_vm, -1), "");
#endif

  // push an instance on the stack to try and match
  //
  DeclareClassBaseMacroClass instance;
  DeclareClassBaseMacroClass *instance_pointer = &instance;
  ASSERT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, instance_pointer));

  // check that match works for a bound instance
  //
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<DeclareClassBaseMacroClass &>(), m_vm, -1));
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<const DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<const DeclareClassBaseMacroClass &>(), m_vm, -1));

  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass &>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass &>(), m_vm, -1));

  sq_poptop(m_vm);

  // check that only pointer matches work for null.
  //
  sq_pushnull(m_vm);

  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<DeclareClassBaseMacroClass &>(), m_vm, -1));
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<const DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const DeclareClassBaseMacroClass &>(), m_vm, -1));

  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass &>(), m_vm, -1));
  EXPECT_TRUE(sqb::Match(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(sqb::Match(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass &>(), m_vm, -1));

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(DeclareClassBaseDeathFixture, TestGet)
{
  // push an instance on the stack to try and get it back
  //
  DeclareClassBaseMacroClass instance;
  DeclareClassBaseMacroClass *instance_pointer = &instance;

  ASSERT_SQ_SUCCEEDED(m_vm, sqb::Push(m_vm, instance_pointer));

  // check that Get works for an instance
  //
  EXPECT_EQ(instance_pointer, sqb::Get(sqb::TypeWrapper<DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_EQ(instance_pointer, &sqb::Get(sqb::TypeWrapper<DeclareClassBaseMacroClass &>(), m_vm, -1));
  EXPECT_EQ(instance_pointer, sqb::Get(sqb::TypeWrapper<const DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_EQ(instance_pointer, &sqb::Get(sqb::TypeWrapper<const DeclareClassBaseMacroClass &>(), m_vm, -1));

  // if asserts are enabled then Get will check for a match otherwise no checking is done
  //
#if SQBIND_ASSERTS_ENABLED != 0
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass *>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass &>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass *>(), m_vm, -1), "");
  EXPECT_DEATH(sqb::Get(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass &>(), m_vm, -1), "");
#else
  UnboundDeclareClassBaseMacroClass *unbound_instance_pointer =
    reinterpret_cast<UnboundDeclareClassBaseMacroClass *>(instance_pointer);

  EXPECT_EQ(unbound_instance_pointer, sqb::Get(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_EQ(unbound_instance_pointer, &sqb::Get(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass &>(), m_vm, -1));
  EXPECT_EQ(unbound_instance_pointer, sqb::Get(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_EQ(unbound_instance_pointer, &sqb::Get(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass &>(), m_vm, -1));
#endif

  sq_poptop(m_vm);

  // check that only pointer Gets work for null.
  //
  sq_pushnull(m_vm);

  EXPECT_EQ(nullptr, sqb::Get(sqb::TypeWrapper<DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(&sqb::Get(sqb::TypeWrapper<DeclareClassBaseMacroClass &>(), m_vm, -1) == nullptr);
  EXPECT_EQ(nullptr, sqb::Get(sqb::TypeWrapper<const DeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(&sqb::Get(sqb::TypeWrapper<const DeclareClassBaseMacroClass &>(), m_vm, -1) == nullptr);

  EXPECT_EQ(nullptr, sqb::Get(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(&sqb::Get(sqb::TypeWrapper<UnboundDeclareClassBaseMacroClass &>(), m_vm, -1) == nullptr);
  EXPECT_EQ(nullptr, sqb::Get(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass *>(), m_vm, -1));
  EXPECT_FALSE(&sqb::Get(sqb::TypeWrapper<const UnboundDeclareClassBaseMacroClass &>(), m_vm, -1) == nullptr);

  sq_poptop(m_vm);
}
